/*
- Support for embedded Exif-GPS information.
-
+ Support for embedded (JPEG) Exif-GPS information.
+
Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org
This program is free software; you can redistribute it and/or modify
*/
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
#include "defs.h"
#include "config.h"
#include "garmin_tables.h"
#include "jeeps/gpsmath.h"
#include "strptime.h"
-#include <ctype.h>
#define MYNAME "exif"
+// #define EXIF_DBG
+
#define UNKNOWN_TIMESTAMP 999999999
+#define IFD0 0
+#define IFD1 1
+#define EXIF_IFD 2 /* dummy index */
+#define GPS_IFD 3 /* dummy index */
+#define INTER_IFD 4 /* dummy index */
+
+#define EXIF_TYPE_BYTE 1
+#define EXIF_TYPE_ASCII 2
+#define EXIF_TYPE_SHORT 3
+#define EXIF_TYPE_LONG 4
+#define EXIF_TYPE_RAT 5
+/* TIFF 6.0 */
+#define EXIF_TYPE_SBYTE 6
+#define EXIF_TYPE_UNK 7
+#define EXIF_TYPE_SSHORT 8
+#define EXIF_TYPE_SLONG 9
+#define EXIF_TYPE_SRAT 10
+#define EXIF_TYPE_FLOAT 11
+#define EXIF_TYPE_DOUBLE 12
+#define EXIF_TYPE_IFD 13
+#define EXIF_TYPE_UNICODE 14
+#define EXIF_TYPE_COMPLEX 15
+#define EXIF_TYPE_LONG8 16
+#define EXIF_TYPE_SLONG8 17
+#define EXIF_TYPE_IFD8 18
+
+#define BYTE_TYPE(a) ( (a==EXIF_TYPE_BYTE) || (a==EXIF_TYPE_ASCII) || (a==EXIF_TYPE_UNK) )
+#define WORD_TYPE(a) ( (a==EXIF_TYPE_SHORT) || (a==EXIF_TYPE_SSHORT) )
+#define LONG_TYPE(a) ( (a==EXIF_TYPE_LONG) || (a==EXIF_TYPE_SLONG) || (a==EXIF_TYPE_IFD) )
+
+#define IFD0_TAG_EXIF_IFD_OFFS 0x8769
+#define IFD0_TAG_GPS_IFD_OFFS 0x8825
+
+#define IFD1_TAG_STRIP_OFFS 0x0111
+#define IFD1_TAG_JPEG_OFFS 0x0201
+#define IFD1_TAG_JPEG_SIZE 0x0202
+
+#define EXIF_IFD_TAG_USER_CMT 0x9286
+#define EXIF_IFD_TAG_INTER_IFD_OFFS 0xA005
+
+#define GPS_IFD_TAG_VERSION 0x0000
+#define GPS_IFD_TAG_LATREF 0x0001
+#define GPS_IFD_TAG_LAT 0x0002
+#define GPS_IFD_TAG_LONREF 0x0003
+#define GPS_IFD_TAG_LON 0x0004
+#define GPS_IFD_TAG_ALTREF 0x0005
+#define GPS_IFD_TAG_ALT 0x0006
+#define GPS_IFD_TAG_TIMESTAMP 0x0007
+#define GPS_IFD_TAG_SAT 0x0008
+#define GPS_IFD_TAG_MODE 0x000A
+#define GPS_IFD_TAG_DOP 0x000B
+#define GPS_IFD_TAG_SPEEDREF 0x000C
+#define GPS_IFD_TAG_SPEED 0x000D
+#define GPS_IFD_TAG_DATUM 0x0012
+#define GPS_IFD_TAG_DATESTAMP 0x001D
+
typedef struct exif_tag_s {
- gbuint16 tag;
+ queue Q;
+ gbuint16 id;
gbuint16 type;
- gbint32 count;
+ gbuint32 count;
+ gbuint32 value;
+ gbuint32 origin;
+ gbuint32 size;
+#ifdef EXIF_DBG
gbuint32 offs;
+#endif
+ unsigned char data_is_dynamic:1;
+ void *data;
} exif_tag_t;
-static gbfile *fin;
-static gbsize_t exif_ifd, gps_ifd;
-static char byte_order;
-static waypoint *wpt;
-static gbsize_t fileoffs;
-static char *opt_filename;
-static time_t timestamp;
-
-static
+typedef struct exif_ifd_s {
+ queue Q;
+ gbuint32 next_ifd;
+ gbuint16 nr;
+ gbuint16 count;
+ queue tags;
+} exif_ifd_t, *exif_ifd_p;
+
+typedef struct exif_app_s {
+ queue Q;
+ gbuint16 marker;
+ gbsize_t len;
+ gbfile *fcache;
+ gbfile *fexif;
+ queue ifds;
+} exif_app_t;
+
+static gbfile *fin, *fout;
+static queue exif_apps;
+static exif_app_t *exif_app;
+const waypoint *exif_wpt_ref;
+time_t exif_time_ref;
+static char exif_success;
+static char *exif_fout_name;
+
+static char *opt_filename, *opt_nobackup, *opt_frame, *opt_name;
+
arglist_t exif_args[] = {
- {"filename", &opt_filename, "Set waypoint name to source filename.", "Y", ARGTYPE_BOOL, ARG_NOMINMAX},
+ { "filename", &opt_filename, "Set waypoint name to source filename.", "Y", ARGTYPE_BOOL, ARG_NOMINMAX },
+ { "frame", &opt_frame, "Time-frame (in seconds).", "10", ARGTYPE_INT, "0", NULL },
+ { "name", &opt_name, "Locate waypoint by this name.", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
+ { "nobackup", &opt_nobackup, "!OVERWRITE! the original file. Default 0=no.", "N", ARGTYPE_BOOL, ARG_NOMINMAX },
ARG_TERMINATOR
};
-#define EXIF_IFD -1
-#define GPS_IFD -2
-
-/*******************************************************************************
-* %%% global callbacks called by gpsbabel main process %%% *
-*******************************************************************************/
-
+#ifdef EXIF_DBG
static void
-exif_rd_init(const char *fname)
+print_buff(const char *buf, int sz, const char *cmt)
{
- fin = gbfopen_le(fname, "rb", MYNAME);
+ int i;
+
+ printf("%s: ", cmt);
+ for (i = 0; i < sz; i++)
+ printf("%02x ", buf[i] & 0xFF);
+ for (i = 0; i < sz; i++) {
+ char c = buf[i];
+ if isspace(c) c = ' ';
+ else if (! isprint(c)) c = '.';
+ printf("%c", c);
+ }
}
+#endif
-static void
-exif_rd_deinit(void)
+static gbuint16
+exif_type_size(const gbuint16 type)
{
- gbfclose(fin);
-}
+ gbuint16 size;
+
+ switch(type) {
+ case EXIF_TYPE_BYTE:
+ case EXIF_TYPE_ASCII:
+ case EXIF_TYPE_UNK: size = 1; break;
+
+ case EXIF_TYPE_SHORT:
+ case EXIF_TYPE_SSHORT:
+ case EXIF_TYPE_UNICODE: size = 2; break;
+ case EXIF_TYPE_IFD:
+ case EXIF_TYPE_LONG:
+ case EXIF_TYPE_SLONG:
+ case EXIF_TYPE_FLOAT: size = 4; break;
+
+ case EXIF_TYPE_RAT:
+ case EXIF_TYPE_SRAT:
+ case EXIF_TYPE_DOUBLE:
+ case EXIF_TYPE_LONG8:
+ case EXIF_TYPE_SLONG8:
+ case EXIF_TYPE_IFD8: size = 8; break;
-#if 0
-static int
-exif_tag_size(const exif_tag_t *tag)
-{
- int size;
-
- switch(tag->type) {
- case 1:
- case 2:
- case 7: size = 1; break;
- case 3: size = 2; break;
- case 4:
- case 9: size = 4; break;
- case 5:
- case 10: size = 8; break;
default:
- return 0;
+ fatal(MYNAME ": Unknown data type %d! Please report.\n", type);
}
- return size * tag->count;
+ return size;
+}
+
+static char *
+exif_time_str(const time_t time)
+{
+ struct tm tm;
+ char *res;
+
+ tm = *localtime(&time);
+ tm.tm_year += 1900;
+ tm.tm_mon += 1;
+ xasprintf(&res, "%04d/%02d/%02d, %02d:%02d:%02d",
+ tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ return res;
+}
+
+static char *
+exif_read_str(exif_tag_t *tag)
+{
+ return xstrndup((char *)tag->data, tag->size);
}
-#endif
static double
-exif_read_double(const gbsize_t offs)
+exif_read_double(const exif_tag_t *tag, const int index)
{
unsigned int num, den;
-
- if (offs) gbfseek(fin, fileoffs + offs, SEEK_SET);
-
- num = gbfgetuint32(fin);
- den = gbfgetuint32(fin);
+ gbint32 *data = (void *)tag->data;
+
+ num = data[index * 2];
+ den = data[(index * 2) + 1];
if (den == 0) den = 1;
-
- return (double)num / den;
+
+ return (double)num / (double)den;
}
static double
exif_read_coord(const exif_tag_t *tag)
{
- double deg, min, sec;
-
- deg = exif_read_double(tag->offs);
- if (tag->count == 1) return deg;
+ double res, min, sec;
- min = exif_read_double(0);
- deg += (min / 60);
- if (tag->count == 2) return deg;
+ res = exif_read_double(tag, 0);
+ if (tag->count == 1) return res;
- sec = exif_read_double(0);
- deg += (sec / 3600);
+ min = exif_read_double(tag, 1);
+ res += (min / 60);
+ if (tag->count == 2) return res;
- return deg;
-}
+ sec = exif_read_double(tag, 2);
+ res += (sec / 3600);
-static char *
-exif_read_string(const exif_tag_t *tag)
-{
- if (tag->count > 4) {
- gbfseek(fin, fileoffs + tag->offs, SEEK_SET);
- return gbfgetcstr(fin);
- }
- else {
- char buff[5];
- if (fin->big_endian) be_write32(buff, tag->offs);
- else le_write32(buff, tag->offs);
- if (tag->count < 5) buff[tag->count] = '\0';
- else buff[4] = '\0';
- return xstrdup(buff);
- }
+ return res;
}
static time_t
exif_read_timestamp(const exif_tag_t *tag)
{
double hour, min, sec;
-
- hour = exif_read_double(tag->offs);
- min = exif_read_double(0);
- sec = exif_read_double(0);
-
+
+ hour = exif_read_double(tag, 0);
+ min = exif_read_double(tag, 1);
+ sec = exif_read_double(tag, 2);
+
return ((int)hour * SECONDS_PER_HOUR) + ((int)min * 60) + (int)sec;
}
-static int
-exif_sort_tags_cb(const void *a, const void *b)
+static time_t
+exif_read_datestamp(const exif_tag_t *tag)
{
- const exif_tag_t *ea = a;
- const exif_tag_t *eb = b;
- return (int)ea->offs - (int)eb->offs;
+ struct tm tm;
+ char *str;
+
+ memset(&tm, 0, sizeof(tm));
+ str = xstrndup((char *)tag->data, tag->size);
+ sscanf(str, "%d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
+ xfree(str);
+
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ return mkgmtime(&tm);
}
-static gbsize_t
-exif_read_tags(const int ifd)
+static void
+exif_release_tag(exif_tag_t *tag)
{
- int entries;
- exif_tag_t *tags = NULL;
- gbsize_t next_ifd = 0;
- double gpsdop = unknown_alt;
- char speed_ref = 'K';
- char lat_ref = '*';
- char lon_ref = '*';
- char mode = 'N';
- int datum = DATUM_WGS84;
-
- entries = gbfgetint16(fin);
- if (entries > 0) {
- int i;
- tags = xmalloc(entries * sizeof(*tags));
- for (i = 0; i < entries; i++) {
- tags[i].tag = gbfgetuint16(fin);
- tags[i].type = gbfgetuint16(fin);
- tags[i].count = gbfgetint32(fin);
- tags[i].offs = gbfgetuint32(fin);
- }
- }
-
- next_ifd = gbfgetuint32(fin);
-
- if (entries > 0) {
- int i;
- char *str, *c;
- struct tm tm;
+ dequeue(&tag->Q);
+ if (tag->data_is_dynamic) xfree(tag->data);
+ xfree(tag);
+}
- if (entries > 1) /* avoid backward seek */
- qsort(tags, entries, sizeof(*tags), exif_sort_tags_cb);
-
- for (i = 0; i < entries; i++) {
- exif_tag_t *tag = &tags[i];
-
- switch(ifd) {
- case 0:
- switch(tag->tag) {
- case 0x8769:
- exif_ifd = tag->offs;
- break;
- case 0x8825:
- gps_ifd = tag->offs;
- break;
- }
- break;
- case 1: /* IFD1 */
- break;
- case -1: /* Exif */
- switch(tag->tag) {
-
- case 0x9003: /* DateTimeOriginal */
- str = exif_read_string(tag);
- c = strptime(str, "%Y:%m:%d %H:%M:%S", &tm);
- if (c && (*c == '\0'))
- wpt->creation_time = mklocaltime(&tm);
- xfree(str);
- break;
- }
- break;
- case -2: /* GPS */
- switch(tag->tag) {
-
- case 0x0001: /* GPSLatitudeRef */
- str = exif_read_string(tag);
- lat_ref = *str & 127;
- xfree(str);
- break;
-
- case 0x0002: /* GPSLatitude */
- wpt->latitude = exif_read_coord(tag);
- break;
-
- case 0x0003: /* GPSLongitudeRef */
- str = exif_read_string(tag);
- lon_ref = *str & 127;
- xfree(str);
- break;
-
- case 0x0004: /* GPSLongitude */
- wpt->longitude = exif_read_coord(tag);
- break;
-
- case 0x0005: /* GPSAltitudeRef */
- break;
-
- case 0x0006: /* GPSAltitude */
- wpt->altitude = exif_read_double(tag->offs);
- break;
-
- case 0x0007: /* GPSTimeStamp */
- timestamp = exif_read_timestamp(tag);
- break;
-
- case 0x0008: /* GPSSatellites */
- str = exif_read_string(tag);
- wpt->sat = atoi(str);
- xfree(str);
- break;
-
- case 0x000a: /* GPSMeasureMode */
- str = exif_read_string(tag);
- mode = *str & 127;
- xfree(str);
- break;
-
- case 0x000b: /* GPSDOP */
- gpsdop = exif_read_double(tag->offs);
- break;
-
- case 0x000c: /* GPSSpeedRef */
- str = exif_read_string(tag);
- speed_ref = *str & 127;
- xfree(str);
- break;
-
- case 0x000d: /* GPSSpeed */
- WAYPT_SET(wpt, speed, exif_read_double(tag->offs));
- break;
-
- case 0x0012: /* GPSMapDatum */
- str = exif_read_string(tag);
- datum = gt_lookup_datum_index(str, MYNAME);
- if (datum < 0)
- fatal(MYNAME ": Unknown GPSMapDatum \"%s\"!\n", str);
- xfree(str);
- break;
- }
- break;
+static void
+exif_release_ifd(exif_ifd_t *ifd)
+{
+ if (ifd != NULL) {
+ queue *elem, *tmp;
+
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_release_tag((exif_tag_t *)elem);
+ }
+ xfree(ifd);
+ }
+}
+
+static void
+exif_release_apps(void)
+{
+ queue *e0, *t0;
+
+ QUEUE_FOR_EACH(&exif_apps, e0, t0) {
+ queue *e1, *t1;
+ exif_app_t *app = (exif_app_t *)dequeue(e0);
+
+ if (app->fcache) gbfclose(app->fcache);
+ if (app->fexif) gbfclose(app->fexif);
+ QUEUE_FOR_EACH(&app->ifds, e1, t1) {
+ exif_ifd_t *ifd = (exif_ifd_t *)dequeue(e1);
+ exif_release_ifd(ifd);
+ }
+ xfree(app);
+ }
+}
+
+static gbuint32
+exif_ifd_size(exif_ifd_t *ifd)
+{
+ queue *elem, *tmp;
+ gbuint32 res = 6; /* nr of tags + next_ifd */
+
+ res += (ifd->count * 12);
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_tag_t *tag = (exif_tag_t *)elem;
+ if (tag->size > 4) {
+ gbuint32 size = tag->size;
+ if (size & 1) size++;
+ res += size;
+ }
+ }
+
+ return res;
+}
+
+static exif_app_t *
+exif_load_apps(void)
+{
+ exif_app_t *exif_app = NULL;
+
+ while (! gbfeof(fin)) {
+ exif_app_t *app = xcalloc(sizeof(*app), 1);
+
+ ENQUEUE_TAIL(&exif_apps, &app->Q);
+ QUEUE_INIT(&app->ifds);
+ app->fcache = gbfopen(NULL, "wb", MYNAME);
+
+ app->marker = gbfgetuint16(fin);
+ app->len = gbfgetuint16(fin);
+#ifdef EXIF_DBG
+ printf(MYNAME ": api = %02X, len = %d, offs = %04X\n", app->marker & 0xFF, app->len, gbftell(fin));
+#endif
+ if (exif_app || (app->marker == 0xFFDA)) /* compressed data */ {
+ gbfcopyfrom(app->fcache, fin, 0x7FFFFFFF);
+#ifdef EXIF_DBG
+ printf(MYNAME ": compressed data size = %d\n", gbftell(app->fcache));
+#endif
+ }
+ else {
+ gbfcopyfrom(app->fcache, fin, app->len - 2);
+ if (app->marker == 0xFFE1) {
+ exif_app = app;
}
}
- xfree(tags);
}
- if (ifd == GPS_IFD) {
+ return exif_app;
+}
+
+static exif_ifd_t *
+exif_read_ifd(exif_app_t *app, const gbuint16 ifd_nr, gbsize_t offs,
+ gbuint32 *exif_ifd_ofs, gbuint32 *gps_ifd_ofs, gbuint32 *inter_ifd_ofs)
+{
+ queue *elem, *tmp;
+ gbuint16 i;
+ exif_ifd_t *ifd;
+ gbfile *fin = app->fexif;
+
+ ifd = xcalloc(sizeof(*ifd), 1);
+ QUEUE_INIT(&ifd->tags);
+ ENQUEUE_TAIL(&app->ifds, &ifd->Q);
+ ifd->nr = ifd_nr;
- /* Did we get our minimum data ? */
- if ((wpt->latitude == unknown_alt) || (wpt->longitude == unknown_alt)) {
- warning(MYNAME ": GPSLatitude and/or GPSLongitude not set!\n");
- waypt_free(wpt);
- wpt = NULL;
- return 0;
+ gbfseek(fin, offs, SEEK_SET);
+ ifd->count = gbfgetuint16(fin);
+
+#ifdef EXIF_DBG
+ {
+ char *name;
+ switch(ifd_nr) {
+ case IFD0: name = "IFD0"; break;
+ case IFD1: name = "IFD1"; break;
+ case GPS_IFD: name = "GPS"; break;
+ case EXIF_IFD: name = "EXIF"; break;
+ case INTER_IFD: name = "INTER"; break;
+ default: name = "private"; break;
}
-
- if WAYPT_HAS(wpt, speed) {
- switch(speed_ref) {
- case 'K':
- wpt->speed = KPH_TO_MPS(wpt->speed);
- break;
- case 'M':
- wpt->speed = MPH_TO_MPS(wpt->speed);
- break;
- case 'N':
- wpt->speed = KNOTS_TO_MPS(wpt->speed);
- break;
- default:
- wpt->speed = 0;
- WAYPT_UNSET(wpt, speed);
- warning(MYNAME ": Unknown GPSSpeedRef unit %c (0x%02x)!\n", speed_ref, speed_ref);
+ printf(MYNAME "-offs 0x%04X: Number of items in IFD%d \"%s\" = %d (0x%2x)\n",
+ offs, ifd_nr, name, ifd->count, ifd->count);
+ }
+#endif
+ if (ifd->count == 0) return ifd;
+
+ for (i = 0; i < ifd->count; i++) {
+ exif_tag_t *tag;
+ offs = gbftell(fin);
+
+ tag = xcalloc(sizeof(*tag), 1);
+
+ ENQUEUE_TAIL(&ifd->tags, &tag->Q);
+
+ tag->id = gbfgetuint16(fin);
+ tag->type = gbfgetuint16(fin);
+ tag->count = gbfgetuint32(fin);
+ tag->size = exif_type_size(tag->type) * tag->count;
+ tag->data = &tag->value;
+#ifdef EXIF_DBG
+ tag->offs = offs;
+#endif
+
+ if (BYTE_TYPE(tag->type) && (tag->count <= 4)) {
+ gbfread(tag->data, 4, 1, fin);
+ }
+ else {
+ tag->value = gbfgetuint32(fin);
+ tag->origin = tag->value;
+ }
+
+ if (ifd_nr == IFD0) {
+ if (tag->id == IFD0_TAG_EXIF_IFD_OFFS) *exif_ifd_ofs = tag->value;
+ else if (tag->id == IFD0_TAG_GPS_IFD_OFFS) *gps_ifd_ofs = tag->value;
+ }
+ else if (ifd_nr == EXIF_IFD) {
+ if (tag->id == EXIF_IFD_TAG_INTER_IFD_OFFS) *inter_ifd_ofs = tag->value;
+ }
+ }
+
+ ifd->next_ifd = gbfgetuint16(fin);
+
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_tag_t *tag = (exif_tag_t *)elem;
+ if ((tag->size > 4) && (tag->value)) {
+ gbuint16 i;
+ void *ptr;
+
+ tag->data = xmalloc(tag->size);
+ tag->data_is_dynamic = 1;
+
+ ptr = (void *)tag->data;
+ gbfseek(fin, tag->value, SEEK_SET);
+
+ if (BYTE_TYPE(tag->type)) gbfread(ptr, tag->count, 1, fin);
+ else for (i = 0; i < tag->count; i++) {
+ switch(tag->type) {
+ case EXIF_TYPE_SHORT:
+ case EXIF_TYPE_SSHORT:
+ *(gbint16 *)ptr = gbfgetuint16(fin);
+ break;
+ case EXIF_TYPE_IFD:
+ case EXIF_TYPE_LONG:
+ case EXIF_TYPE_SLONG:
+ *(gbint32 *)ptr = gbfgetuint32(fin);
+ break;
+ case EXIF_TYPE_RAT:
+ case EXIF_TYPE_SRAT:
+ *(gbint32 *)ptr = gbfgetuint32(fin);
+ *(gbint32 *)(ptr+4) = gbfgetuint32(fin);
+ break;
+ case EXIF_TYPE_FLOAT:
+ *(float *)ptr = gbfgetflt(fin);
+ break;
+ case EXIF_TYPE_DOUBLE:
+ *(double *)ptr = gbfgetdbl(fin);
+ break;
+ default:
+ gbfread(ptr, 1, 1, fin);
+ break;
+ }
+ ptr += (tag->size / tag->count);
}
}
-
- if (lat_ref == 'S') wpt->latitude *= -1;
- else if (lat_ref != 'N') warning(MYNAME ": GPSLatitudeRef not set! Using N(orth).\n");
- if (lon_ref == 'W') wpt->longitude *= -1;
- else if (lon_ref != 'E') warning(MYNAME ": GPSLongitudeRef not set! Using E(east).\n");
-
- if (datum != DATUM_WGS84) {
+#ifdef EXIF_DBG
+ printf(MYNAME "-offs 0x%04X: ifd=%d id=0x%04X t=0x%04X c=%4d s=%4d v=0x%08X",
+ tag->offs, ifd->nr, tag->id, tag->type, tag->count, tag->size, tag->value);
+ if (tag->type == EXIF_TYPE_ASCII) printf(" \"%s\"", exif_read_str(tag));
+ printf("\n");
+#endif
+ }
+
+ return ifd;
+}
+
+static void
+exif_read_app(exif_app_t *app)
+{
+ gbsize_t offs;
+ gbuint32 exif_ifd_ofs, gps_ifd_ofs, inter_ifd_ofs;
+ exif_ifd_t *ifd;
+ gbfile *fin = app->fexif;
+
+#ifdef EXIF_DBG
+ printf(MYNAME ": read_app...\n");
+ print_buff((const char *)fin->handle.mem, 16, MYNAME);
+ printf("\n");
+#endif
+ exif_ifd_ofs = gps_ifd_ofs = inter_ifd_ofs = 0;
+
+ gbfseek(fin, 4, SEEK_SET);
+ offs = gbfgetuint32(fin);
+
+ ifd = exif_read_ifd(app, IFD0, offs, &exif_ifd_ofs, &gps_ifd_ofs, &inter_ifd_ofs);
+ if (ifd == NULL) return;
+
+ if (ifd->next_ifd)
+ ifd = exif_read_ifd(app, IFD1, ifd->next_ifd, &exif_ifd_ofs, &gps_ifd_ofs, &inter_ifd_ofs);
+ if (exif_ifd_ofs)
+ ifd = exif_read_ifd(app, EXIF_IFD, exif_ifd_ofs, NULL, NULL, &inter_ifd_ofs);
+ if (gps_ifd_ofs)
+ ifd = exif_read_ifd(app, 3, gps_ifd_ofs, NULL, NULL, NULL);
+ if (inter_ifd_ofs)
+ ifd = exif_read_ifd(app, 4, inter_ifd_ofs, NULL, NULL, NULL);
+}
+
+static void
+exif_examine_app(exif_app_t *app)
+{
+ gbuint16 endianess;
+ gbuint32 ident;
+ gbfile *ftmp = exif_app->fcache;
+ int i;
+
+ gbfrewind(ftmp);
+ ident = gbfgetuint32(ftmp);
+ is_fatal(ident != 0x66697845, MYNAME ": Invalid EXIF header magic.");
+ is_fatal(gbfgetint16(ftmp) != 0, MYNAME ": Error in EXIF header.");
+ endianess = gbfgetint16(ftmp);
+
+#ifdef EXIF_DBG
+ printf(MYNAME ": endianess = 0x%04X\n", endianess);
+#endif
+ if (endianess == 0x4949) ftmp->big_endian = 0;
+ else if (endianess == 0x4D4D) ftmp->big_endian = 1;
+ else fatal(MYNAME ": Invalid endianess identifier 0x%04X!\n", endianess);
+
+ gbfseek(ftmp, 6, SEEK_SET);
+ app->fexif = gbfopen(NULL, "wb", MYNAME);
+ app->fexif->big_endian = ftmp->big_endian;
+ i = gbfcopyfrom(app->fexif, ftmp, 0x7FFFFFFF);
+
+ exif_read_app(exif_app);
+}
+
+static exif_ifd_t *
+exif_find_ifd(exif_app_t *app, const gbuint16 ifd_nr)
+{
+ queue *e0, *t0;
+
+ QUEUE_FOR_EACH(&app->ifds, e0, t0) {
+ exif_ifd_t *ifd = (exif_ifd_t *)e0;
+
+ if (ifd->nr == ifd_nr) return ifd;
+ }
+ return NULL;
+}
+
+static exif_tag_t *
+exif_find_tag(exif_app_t *app, const gbuint16 ifd_nr, const gbuint16 tag_id)
+{
+ exif_ifd_t *ifd = exif_find_ifd(app, ifd_nr);
+ if (ifd != NULL) {
+ queue *elem, *tmp;
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_tag_t *tag = (exif_tag_t *)elem;
+ if (tag->id == tag_id) return tag;
+ }
+ }
+ return NULL;
+}
+
+static time_t
+exif_get_exif_time(exif_app_t *app)
+{
+ exif_tag_t *tag;
+ time_t res = 0;
+
+ tag = exif_find_tag(app, EXIF_IFD, 0x9003); /* DateTimeOriginal from EXIF */
+ if (! tag) tag = exif_find_tag(app, IFD0, 0x0132); /* DateTime from IFD0 */
+ if (! tag) tag = exif_find_tag(app, EXIF_IFD, 0x9004); /* DateTimeDigitized from EXIF */
+ if (tag) {
+ struct tm tm;
+ char *c, *str;
+
+ memset(&tm, 0, sizeof(tm));
+ str = exif_read_str(tag);
+ c = strptime(str, "%Y:%m:%d %H:%M:%S", &tm);
+ if (c && (*c == '\0')) res = mklocaltime(&tm);
+
+ xfree(str);
+ }
+ return res;
+}
+
+static waypoint *
+exif_waypt_from_exif_app(exif_app_t *app)
+{
+ waypoint *wpt;
+ queue *elem, *tmp;
+ exif_ifd_t *ifd;
+ exif_tag_t *tag;
+ char lat_ref = '\0';
+ char lon_ref = '\0';
+ char alt_ref = 0;
+ char speed_ref = '\0';
+ char *datum = NULL;
+ char mode = '\0';
+ double gpsdop = unknown_alt;
+ double alt = unknown_alt;
+ time_t timestamp = UNKNOWN_TIMESTAMP;
+ time_t datestamp = UNKNOWN_TIMESTAMP;
+
+ ifd = exif_find_ifd(app, GPS_IFD);
+ if (ifd == NULL) return NULL;
+
+ wpt = waypt_new();
+
+ wpt->latitude = unknown_alt;
+ wpt->longitude = unknown_alt;
+
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ tag = (exif_tag_t *)elem;
+
+ switch(tag->id) {
+ case GPS_IFD_TAG_VERSION: break;
+ case GPS_IFD_TAG_LATREF: lat_ref = *(char *)tag->data; break;
+ case GPS_IFD_TAG_LAT: wpt->latitude = exif_read_coord(tag); break;
+ case GPS_IFD_TAG_LONREF: lon_ref = *(char *)tag->data; break;
+ case GPS_IFD_TAG_LON: wpt->longitude = exif_read_coord(tag); break;
+ case GPS_IFD_TAG_ALTREF: alt_ref = *(char *)tag->data; break;
+ case GPS_IFD_TAG_ALT: alt = exif_read_double(tag, 0); break;
+ case GPS_IFD_TAG_TIMESTAMP: timestamp = exif_read_timestamp(tag); break;
+ case GPS_IFD_TAG_SAT: wpt->sat = atoi((char *)tag->data); break;
+ case GPS_IFD_TAG_MODE: mode = *(char *)tag->data; break;
+ case GPS_IFD_TAG_DOP: gpsdop = exif_read_double(tag, 0); break;
+ case GPS_IFD_TAG_SPEEDREF: speed_ref = *(char *)tag->data; break;
+ case GPS_IFD_TAG_SPEED: WAYPT_SET(wpt, speed, exif_read_double(tag, 0)); break;
+ case GPS_IFD_TAG_DATUM: datum = exif_read_str(tag); break;
+ case GPS_IFD_TAG_DATESTAMP: datestamp = exif_read_datestamp(tag); break;
+ }
+ }
+
+ if ((wpt->latitude == unknown_alt) || (wpt->longitude == unknown_alt))
+ fatal(MYNAME ": Missing GPSLatitude and/or GPSLongitude!\n");
+
+ if (lat_ref == 'S')
+ wpt->latitude *= -1;
+ else if (lat_ref != 'N')
+ warning(MYNAME ": GPSLatitudeRef not set! Using N(orth).\n");
+
+ if (lon_ref == 'W')
+ wpt->longitude *= -1;
+ else if (lon_ref != 'E')
+ warning(MYNAME ": GPSLongitudeRef not set! Using E(east).\n");
+
+#ifdef EXIF_DBG
+ printf(MYNAME "-GPSLatitude = %12.7f\n", wpt->latitude);
+ printf(MYNAME "-GPSLongitude = %12.7f\n", wpt->longitude);
+#endif
+ if (datum) {
+ int idatum = gt_lookup_datum_index(datum, MYNAME);
+ if (idatum < 0)
+ fatal(MYNAME ": Unknown GPSMapDatum \"%s\"!\n", datum);
+ if (idatum != DATUM_WGS84) {
double alt;
GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0,
- &wpt->latitude, &wpt->longitude, &alt, datum);
+ &wpt->latitude, &wpt->longitude, &alt, idatum);
+ }
+ xfree(datum);
+ }
+
+ if (alt != unknown_alt) {
+ if (alt_ref != 0)
+ warning(MYNAME ": Invalid GPSAltitudeRef (%d)! Using 0 (= Sea level).\n", alt_ref);
+ wpt->altitude = alt;
+#ifdef EXIF_DBG
+ printf(MYNAME "-GPSAltitude = %12.7f m\n", wpt->altitude);
+#endif
+ }
+
+ if WAYPT_HAS(wpt, speed) {
+ switch(speed_ref) {
+ case 'K':
+ wpt->speed = KPH_TO_MPS(wpt->speed);
+ break;
+ case 'M':
+ wpt->speed = MPH_TO_MPS(wpt->speed);
+ break;
+ case 'N':
+ wpt->speed = KNOTS_TO_MPS(wpt->speed);
+ break;
+ default:
+ wpt->speed = 0;
+ WAYPT_UNSET(wpt, speed);
+ warning(MYNAME ": Unknown GPSSpeedRef unit %c (0x%02x)!\n", speed_ref, speed_ref);
+ }
+#ifdef EXIF_DBG
+ if WAYPT_HAS(wpt, speed) printf(MYNAME "-GPSSpeed = %12.2f m/s\n", wpt->speed);
+#endif
+ }
+
+ if (mode == '2') {
+ wpt->fix = fix_2d;
+ if (gpsdop != unknown_alt) wpt->hdop = gpsdop;
+ }
+ else if (mode == '3') {
+ wpt->fix = fix_3d;
+ if (gpsdop != unknown_alt) wpt->pdop = gpsdop;
+ }
+
+ if (timestamp != UNKNOWN_TIMESTAMP) {
+ if (datestamp != UNKNOWN_TIMESTAMP) timestamp += datestamp;
+ }
+ else timestamp = datestamp;
+ if (timestamp != UNKNOWN_TIMESTAMP) {
+#ifdef EXIF_DBG
+ char *str = exif_time_str(timestamp);
+ printf(MYNAME "-GPSTimeStamp = %s\n", str);
+ xfree(str);
+#endif
+ wpt->creation_time = timestamp;
+ }
+ else
+ wpt->creation_time = exif_get_exif_time(app);
+
+ tag = exif_find_tag(app, EXIF_IFD, EXIF_IFD_TAG_USER_CMT); /* UserComment */
+ if (tag && (tag->size > 8)) {
+ char *str = NULL;
+ if (memcmp(tag->data, "ASCII\0\0\0", 8) == 0) {
+ str = xstrndup(tag->data + 8, tag->size - 8);
}
-
- if (mode == '2') {
- wpt->fix = fix_2d;
- if (gpsdop != unknown_alt) wpt->hdop = gpsdop;
+ else if (memcmp(tag->data, "UNICODE\0", 8) == 0) {
+ int i, len = (tag->size - 8) / 2;
+ gbint16 *s = tag->data + 8;
+ for (i = 0; i < len; i++) s[i] = be_read16(&s[i]); /* always BE ? */
+ str = cet_str_uni_to_any(s, len, global_opts.charset);
}
- else if (mode == '3') {
- wpt->fix = fix_3d;
- if (gpsdop != unknown_alt) wpt->pdop = gpsdop;
+ if (str != NULL) {
+ wpt->notes = str;
}
}
- return next_ifd;
+ if (opt_filename) {
+ char *c, *cx;
+ char *str = xstrdup(fin->name);
+
+ cx = str;
+ if ((c = strrchr(cx, ':'))) cx = c + 1;
+ if ((c = strrchr(cx, '\\'))) cx = c + 1;
+ if ((c = strrchr(cx, '/'))) cx = c + 1;
+ if (((c = strchr(cx, '.'))) && (c != cx)) *c = '\0';
+
+ if (wpt->shortname) xfree(wpt->shortname);
+ wpt->shortname = xstrdup(cx);
+ xfree(str);
+ }
+
+ return wpt;
}
static void
-exif_read(void)
+exif_dec2frac(double val, int *num, int *den)
+{
+ char sval[16], snum[16];
+ char dot = 0;
+ int den1 = 1;
+ int num1, num2, den2, rem;
+ char *cx;
+ double vx;
+
+ if (val < 0.000000001) val = 0.0;
+ else if (val > 999999999.0)
+ fatal(MYNAME ": Value (%f) to big for a rational representation!\n", val);
+
+ num1 = 0;
+ vx = fabs(val);
+ while (vx > 1) {
+ num1++;
+ vx = vx / 10;
+ }
+
+ snprintf(sval, sizeof(sval), "%*.*f", 9, 9 - num1, fabs(val));
+ snum[0] = '\0';
+
+ cx = sval;
+ while (*cx) {
+ if (dot) den1 *= 10;
+ if (*cx == '.') dot = 1;
+ else strncat(snum, cx, 1);
+ cx++;
+ }
+
+ num1 = atoi(snum);
+ if (den1 == 1) {
+ *num = num1;
+ *den = den1;
+ }
+
+ num2 = num1;
+ den2 = den1;
+ rem = 1;
+
+ /* Euclid's Algorithm to find the gcd */
+ while (num2 % den2) {
+ rem = num2 % den2;
+ num2 = den2;
+ den2 = rem;
+ }
+ if (den2 != den1) rem = den2;
+
+ *num = num1 / rem;
+ *den = den1 / rem;
+}
+
+static exif_tag_t *
+exif_put_value(const int ifd_nr, const gbuint16 tag_id, const gbuint16 type, const gbuint32 count, const int index, const void *data)
{
- gbint32 code = 0;
-
- fileoffs = 0;
- wpt = NULL;
-
- while (!gbfeof(fin)) {
-
- unsigned char c = (unsigned)gbfgetc(fin);
-
- code = (code << 8) | c;
- if (code == 0x45786966) { /* Look for "Exif" */
-
- gbsize_t next_ifd;
- gbint32 ifd;
-
- int order = gbfgetint32(fin);
- switch(order) {
- case 0x49490000: /* "II" - Intel */
- byte_order = 'I';
- break;
- case 0x4D4D0000: /* "MM" - Motorola */
- byte_order = 'M';
- break;
-
- default:
- continue;
+ exif_tag_t *tag = NULL;
+ exif_ifd_t *ifd;
+ gbuint16 item_size, size;
+
+ ifd = exif_find_ifd(exif_app, ifd_nr);
+ if (ifd == NULL) {
+ ifd = xcalloc(sizeof(*ifd), 1);
+ ifd->nr = ifd_nr;
+ QUEUE_INIT(&ifd->tags);
+ ENQUEUE_TAIL(&exif_app->ifds, &ifd->Q);
+ }
+ else tag = exif_find_tag(exif_app, ifd_nr, tag_id);
+
+ item_size = exif_type_size(type);
+
+ if ((data == NULL) || (count < 1) || (index < 0)) size = 0;
+ else size = (index + count) * item_size;
+
+ if (tag == NULL) {
+ if (size == 0) return NULL;
+
+ tag = xcalloc(sizeof(*tag), 1);
+
+ tag->id = tag_id;
+ tag->type = type;
+ tag->count = index + count;
+ tag->size = size;
+ tag->data = xcalloc((size < 4) ? 4 : size, 1);
+ tag->data_is_dynamic = 1;
+ ifd->count++;
+
+ ENQUEUE_TAIL(&ifd->tags, &tag->Q);
+ }
+ else {
+ if (size == 0) { /* remove this element */
+ ifd->count--;
+ exif_release_tag(tag);
+ return NULL;
+ }
+ if (tag->count < (index + count)) {
+ if (! tag->data_is_dynamic) {
+ void *tmp = xmalloc(tag->size < 4 ? 4 : tag->size);
+ memcpy(tmp, tag->data, tag->size);
+ tag->data = tmp;
+ tag->data_is_dynamic = 1;
}
- fin->big_endian = (byte_order == 'M');
- if (gbfgetint16(fin) != 0x002a) continue;
-
- fileoffs = gbftell(fin) - 4;
-
- next_ifd = gbfgetuint32(fin);
- if (! next_ifd) continue; /* No IFD0 ? */
-
- ifd = gps_ifd = exif_ifd = 0;
- timestamp = UNKNOWN_TIMESTAMP;
-
- /* we need the wpt not only during GPS IFD */
- wpt = waypt_new();
- wpt->latitude = unknown_alt;
- wpt->longitude = unknown_alt;
-
- while (next_ifd) {
- gbfseek(fin, fileoffs + next_ifd, SEEK_SET);
- next_ifd = exif_read_tags(ifd++);
+ tag->size = size;
+ tag->count = index + count;
+ tag->data = xrealloc(tag->data, size < 4 ? 4 : size);
+ }
+ }
+
+ switch(type) {
+ case EXIF_TYPE_RAT:
+ case EXIF_TYPE_SRAT: {
+ double val = *(double *)data;
+ gbuint32 *dest = tag->data;
+
+ if ((int)val == val) {
+ dest[index * 2] = (int)val;
+ dest[(index * 2) + 1] = 1;
}
- if (exif_ifd) {
- gbfseek(fin, fileoffs + exif_ifd, SEEK_SET);
- (void) exif_read_tags(EXIF_IFD);
+ else {
+ gbint32 Nom, Den;
+ exif_dec2frac(val, &Nom, &Den);
+ if ((type == EXIF_TYPE_SRAT) && (val < 0.0)) Nom *= -1;
+ dest[index * 2] = Nom;
+ dest[(index * 2) + 1] = Den;
}
- if (gps_ifd) {
- gbfseek(fin, fileoffs + gps_ifd, SEEK_SET);
- (void) exif_read_tags(GPS_IFD);
}
- else {
- warning(MYNAME ": No Exif-GPS information in file \"%s\"!\n", fin->name);
- waypt_free(wpt);
- wpt = NULL;
+ break;
+ default: {
+ char *dest = tag->data;
+ memcpy(&dest[index * item_size], data, count * item_size);
}
-
- if (! wpt) return;
-
- if (wpt->creation_time && (timestamp != UNKNOWN_TIMESTAMP)) {
- struct tm tm;
- tm = *gmtime(&wpt->creation_time);
- tm.tm_hour = 0;
- tm.tm_min = 0;
- tm.tm_sec = 0;
- wpt->creation_time = mkgmtime(&tm) + timestamp;
+ }
+ return tag;
+}
+
+
+static void
+exif_put_double(const int ifd_nr, const int tag_id, const int index, const double val)
+{
+ double d = fabs(val);
+ exif_put_value(ifd_nr, tag_id, EXIF_TYPE_RAT, 1, index, &d);
+}
+
+
+static void
+exif_put_str(const int ifd_nr, const int tag_id, const char *val)
+{
+ int len = (val) ? strlen(val) + 1 : 0;
+ exif_put_value(ifd_nr, tag_id, EXIF_TYPE_ASCII, len, 0, val);
+}
+
+static void
+exif_put_coord(const int ifd_nr, const int tag_id, const double val)
+{
+ double vmin, vsec;
+ int vint;
+
+ vint = abs((int) val);
+ vmin = 60.0 * (fabs(val) - vint);
+ vsec = 60.0 * (vmin - floor(vmin));
+
+ exif_put_double(ifd_nr, tag_id, 0, (double)vint);
+ exif_put_double(ifd_nr, tag_id, 1, (double)vmin);
+ exif_put_double(ifd_nr, tag_id, 2, (double)vsec);
+}
+
+static void
+exif_put_long(const int ifd_nr, const int tag_id, const int index, const gbint32 val)
+{
+ exif_put_value(ifd_nr, tag_id, EXIF_TYPE_LONG, 1, index, &val);
+}
+
+static void
+exif_remove_tag(const int ifd_nr, const int tag_id)
+{
+ exif_put_value(ifd_nr, tag_id, EXIF_TYPE_BYTE, 0, 0, NULL);
+}
+
+static void
+exif_find_wpt_by_time(const waypoint *wpt)
+{
+ if (wpt->creation_time <= 0) return;
+
+ if (exif_wpt_ref == NULL) exif_wpt_ref = wpt;
+ else if (abs(exif_time_ref - wpt->creation_time) < abs(exif_time_ref - exif_wpt_ref->creation_time))
+ exif_wpt_ref = wpt;
+}
+
+static void
+exif_find_wpt_by_name(const waypoint *wpt)
+{
+ if (exif_wpt_ref != NULL)
+ return;
+ else if ((wpt->shortname != NULL) && (case_ignore_strcmp(wpt->shortname, opt_name) == 0))
+ exif_wpt_ref = wpt;
+}
+
+
+static int
+exif_sort_tags_cb(const queue *A, const queue *B)
+{
+ exif_tag_t *ta = (exif_tag_t *)A;
+ exif_tag_t *tb = (exif_tag_t *)B;
+
+ return ta->id - tb->id;
+}
+
+static int
+exif_sort_ifds_cb(const queue *A, const queue *B)
+{
+ exif_ifd_t *ia = (exif_ifd_t *)A;
+ exif_ifd_t *ib = (exif_ifd_t *)B;
+
+ return ia->nr - ib->nr;
+}
+
+static void
+exif_write_value(exif_tag_t *tag, gbfile *fout)
+{
+ if (tag->size > 4) gbfputuint32(tag->value, fout); /* offset to data */
+ else {
+ void *data = tag->data;
+
+ if BYTE_TYPE(tag->type) gbfwrite(data, 4, 1, fout);
+ else if WORD_TYPE(tag->type) {
+ gbfputuint16(*(gbuint16 *)data, fout);
+ gbfputuint16(*(gbuint16 *)(data+2), fout);
+ }
+ else if LONG_TYPE(tag->type) gbfputuint32(*(gbuint32 *)data, fout);
+ else if (tag->type == EXIF_TYPE_FLOAT) gbfputflt(*(float *)data, fout);
+ else fatal(MYNAME ": Unknown data type %d!\n", tag->type);
+ }
+}
+
+static void
+exif_write_ifd(const exif_ifd_t *ifd, const char next, gbfile *fout)
+{
+ gbsize_t offs;
+ queue *elem, *tmp;
+
+ gbfputuint16(ifd->count, fout);
+ offs = gbftell(fout) + (ifd->count * 12) + 4;
+
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_tag_t *tag = (exif_tag_t *)elem;
+
+ gbfputuint16(tag->id, fout);
+ gbfputuint16(tag->type, fout);
+ gbfputuint32(tag->count, fout);
+ if (tag->size > 4) {
+ tag->value = offs;
+ offs += tag->size;
+ if (offs & 1) offs++;
+ gbfputuint32(tag->value, fout);
+ }
+ else exif_write_value(tag, fout);
+ }
+
+ if (next) gbfputuint32(offs, fout);
+ else gbfputuint32(0, fout);
+
+ QUEUE_FOR_EACH(&ifd->tags, elem, tmp) {
+ exif_tag_t *tag = (exif_tag_t *)elem;
+
+ if (tag->size > 4) {
+ gbuint16 i;
+ void *ptr = tag->data;
+
+ if BYTE_TYPE(tag->type) gbfwrite(tag->data, tag->size, 1, fout);
+ else for (i = 0; i < tag->count; i++) {
+ switch(tag->type) {
+ case EXIF_TYPE_SHORT:
+ case EXIF_TYPE_SSHORT:
+ gbfputuint16(*(gbint16 *)ptr, fout);
+ break;
+ case EXIF_TYPE_LONG:
+ case EXIF_TYPE_SLONG:
+ case EXIF_TYPE_IFD:
+ gbfputuint32(*(gbint32 *)ptr, fout);
+ break;
+ case EXIF_TYPE_RAT:
+ case EXIF_TYPE_SRAT:
+ gbfputuint32(*(gbint32 *)ptr, fout);
+ gbfputuint32(*(gbint32 *)(ptr+4), fout);
+ break;
+ case EXIF_TYPE_FLOAT:
+ gbfputflt(*(float *)ptr, fout);
+ break;
+ case EXIF_TYPE_DOUBLE:
+ gbfputdbl(*(double *)ptr, fout);
+ break;
+ default:
+ gbfwrite(ptr, exif_type_size(tag->type), 1, fin);
+ break;
+ }
+ ptr += (tag->size / tag->count);
}
+ if (gbftell(fout) & 1) gbfputc(0, fout);
+ }
+ }
+}
- if (opt_filename) {
- char *c, *cx;
- char *str = xstrdup(fin->name);
+static void
+exif_write_apps(void)
+{
+ queue *e0, *t0;
- cx = str;
- if ((c = strrchr(cx, ':'))) cx = c + 1;
- if ((c = strrchr(cx, '\\'))) cx = c + 1;
- if ((c = strrchr(cx, '/'))) cx = c + 1;
- if (((c = strchr(cx, '.'))) && (c != cx)) *c = '\0';
+ gbfputuint16(0xFFD8, fout);
- if (wpt->shortname) xfree(wpt->shortname);
- wpt->shortname = xstrdup(cx);
- xfree(str);
+ QUEUE_FOR_EACH(&exif_apps, e0, t0) {
+ exif_app_t *app = (exif_app_t *)e0;
+
+ gbfputuint16(app->marker, fout);
+
+ if (app == exif_app) {
+ queue *e1, *t1;
+ gbuint16 len = 8;
+ gbfile *ftmp;
+ exif_tag_t *tag;
+
+ exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, 0);
+ exif_put_long(GPS_IFD, GPS_IFD_TAG_VERSION, 0, 2);
+
+ sortqueue(&exif_app->ifds, exif_sort_ifds_cb);
+
+ QUEUE_FOR_EACH(&app->ifds, e1, t1) {
+ exif_ifd_t *ifd = (exif_ifd_t *)e1;
+
+ if (ifd->nr == GPS_IFD) exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, len);
+ else if (ifd->nr == EXIF_IFD) exif_put_long(IFD0, IFD0_TAG_EXIF_IFD_OFFS, 0, len);
+ else if (ifd->nr == INTER_IFD) exif_put_long(EXIF_IFD, EXIF_IFD_TAG_INTER_IFD_OFFS, 0, len);
+
+ len += exif_ifd_size(ifd);
}
- waypt_add(wpt);
-
- return;
+
+ len += 4; /* DWORD(0) after last ifd */
+
+ if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS)))
+ exif_put_long(IFD1, IFD1_TAG_JPEG_OFFS, 0, len);
+
+ QUEUE_FOR_EACH(&app->ifds, e1, t1) {
+ exif_ifd_t *ifd = (exif_ifd_t *)e1;
+ sortqueue(&ifd->tags, exif_sort_tags_cb);
+ }
+
+ ftmp = gbfopen_be(NULL, "wb", MYNAME);
+ ftmp->big_endian = app->fcache->big_endian;
+
+ gbfwrite((ftmp->big_endian) ? "MM" : "II", 2, 1, ftmp);
+ gbfputuint16(0x2A, ftmp);
+ gbfputuint32(0x08, ftmp); /* offset to first IFD */
+
+ QUEUE_FOR_EACH(&app->ifds, e1, t1) {
+ exif_ifd_t *ifd = (exif_ifd_t *)e1;
+ exif_ifd_t *ifd_next = (exif_ifd_t *)t1;
+ char next;
+
+ if ((ifd->nr == IFD0) && (ifd_next->nr == IFD1)) next = 1;
+ else next = 0;
+
+ exif_write_ifd(ifd, next, ftmp);
+ len = gbftell(ftmp);
+ }
+
+ gbfputuint32(0, ftmp); /* DWORD(0) after last ifd */
+
+ if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) {
+ gbsize_t offs = tag->origin;
+ if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_SIZE))) {
+ gbfseek(app->fexif, offs, SEEK_SET);
+ gbfcopyfrom(ftmp, app->fexif, tag->value);
+ }
+ }
+
+ len = gbftell(ftmp);
+ gbfrewind(ftmp);
+ gbfputuint16(len + 8, fout);
+ gbfwrite("Exif\0\0", 6, 1, fout);
+ gbfcopyfrom(fout, ftmp, len);
+
+ gbfclose(ftmp);
+ }
+ else {
+ gbfputuint16(app->len, fout);
+ gbfrewind(app->fcache);
+ gbfcopyfrom(fout, app->fcache, 0x7FFFFFFF);
}
}
- warning(MYNAME ": No Exif header in file \"%s\"!\n", fin->name);
}
-#if 0
+/*******************************************************************************
+* %%% global callbacks called by gpsbabel main process %%% *
+*******************************************************************************/
+
+static void
+exif_rd_init(const char *fname)
+{
+ fin = gbfopen_be(fname, "rb", MYNAME);
+ QUEUE_INIT(&exif_apps);
+}
+
+static void
+exif_rd_deinit(void)
+{
+ exif_release_apps();
+ gbfclose(fin);
+}
+
+static void
+exif_read(void)
+{
+ gbuint16 soi;
+ waypoint *wpt;
+
+ soi = gbfgetuint16(fin);
+ is_fatal(soi != 0xFFD8, MYNAME ": Unknown image file."); /* only jpeg for now */
+
+ exif_app = exif_load_apps();
+ is_fatal(exif_app == NULL, MYNAME ": No EXIF header in source file \"%s\".", fin->name);
+
+ exif_examine_app(exif_app);
+ wpt = exif_waypt_from_exif_app(exif_app);
+ if (wpt) waypt_add(wpt);
+}
+
static void
exif_wr_init(const char *fname)
{
- fout = gbfopen(fname, "w", MYNAME);
+ gbuint16 soi;
+ char *tmpname;
+
+ exif_success = 0;
+ exif_fout_name = xstrdup(fname);
+
+ QUEUE_INIT(&exif_apps);
+
+ fin = gbfopen_be(fname, "rb", MYNAME);
+ is_fatal(fin->is_pipe, MYNAME ": Sorry, this format cannot be used with pipes!");
+
+ soi = gbfgetuint16(fin);
+ is_fatal(soi != 0xFFD8, MYNAME ": Unknown image file.");
+ exif_app = exif_load_apps();
+ is_fatal(exif_app == NULL, MYNAME ": No EXIF header found in source file \"%s\".", fin->name);
+ exif_examine_app(exif_app);
+ gbfclose(fin);
+
+ exif_time_ref = exif_get_exif_time(exif_app);
+ if (exif_time_ref == 0) fatal(MYNAME ": No valid timestamp found in picture!\n");
+
+ xasprintf(&tmpname, "%s.jpg", fname);
+ fout = gbfopen_be(tmpname, "wb", MYNAME);
+ xfree(tmpname);
}
static void
exif_wr_deinit(void)
{
+ char *tmpname;
+
+ exif_release_apps();
+ tmpname = xstrdup(fout->name);
gbfclose(fout);
+
+ if (exif_success) {
+ if (*opt_nobackup == '1') {
+ remove(exif_fout_name);
+ rename(tmpname, exif_fout_name);
+ }
+ }
+ else remove(tmpname);
+
+ xfree(exif_fout_name);
+ xfree(tmpname);
}
static void
exif_write(void)
{
+ exif_wpt_ref = NULL;
+ char alt_ref = 0;
+ time_t frame;
+
+ if (opt_name) {
+ waypt_disp_all(exif_find_wpt_by_name);
+ if (exif_wpt_ref == NULL) route_disp_all(NULL, NULL, exif_find_wpt_by_name);
+ if (exif_wpt_ref == NULL) track_disp_all(NULL, NULL, exif_find_wpt_by_name);
+ if (exif_wpt_ref == NULL) {
+ warning(MYNAME ": No matching point with name \"%s\" found.\n", opt_name);
+ }
+ }
+ else {
+ char *str = exif_time_str(exif_time_ref);
+
+ track_disp_all(NULL, NULL, exif_find_wpt_by_time);
+ route_disp_all(NULL, NULL, exif_find_wpt_by_time);
+ waypt_disp_all(exif_find_wpt_by_time);
+
+ frame = atoi(opt_frame);
+
+ if (exif_wpt_ref == NULL)
+ warning(MYNAME ": No point with a valid timestamp found.\n");
+ else if (abs(exif_time_ref - exif_wpt_ref->creation_time) > frame) {
+ warning(MYNAME ": No matching point found for image date %s!\n", str);
+ if (exif_wpt_ref != NULL) {
+ char *str = exif_time_str(exif_wpt_ref->creation_time);
+ warning(MYNAME ": Best is from %s, %d second(s) away.\n",
+ str, abs(exif_time_ref - exif_wpt_ref->creation_time));
+ xfree(str);
+ }
+ exif_wpt_ref = NULL;
+ }
+ xfree(str);
+ }
+
+ if (exif_wpt_ref != NULL) {
+ const waypoint *wpt = exif_wpt_ref;
+
+ exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, 0);
+ exif_put_long(GPS_IFD, GPS_IFD_TAG_VERSION, 0, 2);
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_DATUM, "WGS-84");
+
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_LATREF, wpt->latitude < 0 ? "S" : "N");
+ exif_put_coord(GPS_IFD, GPS_IFD_TAG_LAT, fabs(wpt->latitude));
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_LONREF, wpt->longitude < 0 ? "W" : "E");
+ exif_put_coord(GPS_IFD, GPS_IFD_TAG_LON, fabs(wpt->longitude));
+
+ if (wpt->altitude == unknown_alt) {
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_ALT);
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_ALTREF);
+ }
+ else {
+ exif_put_value(GPS_IFD, GPS_IFD_TAG_ALTREF, EXIF_TYPE_BYTE, 1, 0, &alt_ref);
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_ALT, 0, wpt->altitude);
+ }
+
+ if (wpt->creation_time) {
+ struct tm tm;
+ char buf[32];
+
+ tm = *gmtime(&wpt->creation_time);
+ tm.tm_year += 1900;
+ tm.tm_mon += 1;
+
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 0, tm.tm_hour);
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 1, tm.tm_min);
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, tm.tm_sec);
+
+ snprintf(buf, sizeof(buf), "%04d:%02d:%02d", tm.tm_year, tm.tm_mon, tm.tm_mday);
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_DATESTAMP, buf);
+ }
+ else {
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_TIMESTAMP);
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_DATESTAMP);
+ }
+
+ if (wpt->sat > 0) {
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%d", wpt->sat);
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_SAT, buf);
+ }
+ else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SAT);
+
+ if (wpt->fix == fix_2d) exif_put_str(GPS_IFD, GPS_IFD_TAG_MODE, "2");
+ else if (wpt->fix == fix_3d) exif_put_str(GPS_IFD, GPS_IFD_TAG_MODE, "3");
+ else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_MODE);
+
+ if (wpt->hdop > 0) exif_put_double(GPS_IFD, GPS_IFD_TAG_DOP, 0, wpt->hdop);
+ else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_DOP);
+
+ if WAYPT_HAS(wpt, speed) {
+ exif_put_str(GPS_IFD, GPS_IFD_TAG_SPEEDREF, "K");
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_SPEED, 0, MPS_TO_KPH(wpt->speed));
+ }
+ else {
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SPEEDREF);
+ exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SPEED);
+ }
+
+ exif_write_apps(); /* Success, write the new file */
+
+ exif_success = 1;
+ }
+
}
-#endif
/**************************************************************************/
ff_vecs_t exif_vecs = {
ff_type_file,
- {
- ff_cap_read /* waypoints */,
- ff_cap_none /* tracks */,
- ff_cap_none /* routes */
+ {
+ ff_cap_read | ff_cap_write /* waypoints */,
+ ff_cap_none /* tracks */,
+ ff_cap_none /* routes */
},
exif_rd_init,
- NULL, /* exif_wr_init, */
- exif_rd_deinit,
- NULL, /* exif_wr_deinit, */
+ exif_wr_init,
+ exif_rd_deinit,
+ exif_wr_deinit,
exif_read,
- NULL, /* exif_write */
+ exif_write,
NULL,
exif_args,
- CET_CHARSET_ASCII, 0
+ CET_CHARSET_UTF8, 0
};
/**************************************************************************/